/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package simhya.model.stochastic;

import java.util.ArrayList;
import cern.colt.Arrays;
import simhya.model.store.Function;
import simhya.model.store.Predicate;
import simhya.model.transition.AtomicReset;
import simhya.model.transition.Transition;
import simhya.model.store.Store;
import simhya.model.ModelException;
import simhya.model.transition.TType;
import simhya.GlobalOptions;

/**
 *
 * @author Luca
 */
public class BasicStochasticModel implements StochasticModel {
    private String name;
    private Store store;
    
    private Transition[] transitions;
    private int[] stochasticTransitionID;
    private int[] instantaneousTransitionID;
    private int[] timedTransitionID;
    
    private Integer[][] guardDependencyGraph;
    private Integer[][] rateDependencyGraph;
    private Integer[][] firingTimeDependencyGraph;
    boolean initialized;
    
    int numberOfTransitions;
    int numberOfStochasticTransitions;
    int numberOfInstantaneousTransitions;
    int numberOfTimedTransitions;

    double[] initialValueOfVariables;
    double[] initialValueOfParameters;
    
    public BasicStochasticModel(String name) {
        this.name = name;
        
        this.transitions = new Transition[GlobalOptions.initialArraySize];
        this.stochasticTransitionID = new int[GlobalOptions.initialArraySize];
        this.instantaneousTransitionID = new int[GlobalOptions.initialArraySize];
        this.timedTransitionID = new int[GlobalOptions.initialArraySize];
        
        this.store = null;
        this.guardDependencyGraph = null;
        this.firingTimeDependencyGraph = null;
        this.rateDependencyGraph = null;
        this.initialized = false;
        this.numberOfTransitions = 0;
        numberOfStochasticTransitions = 0;
        numberOfInstantaneousTransitions = 0;
        numberOfTimedTransitions = 0;
    }
    /**
     * add a store to the model
     * @param store
     */
    public void setStore(Store store) {
        this.store = store;
        this.initialValueOfVariables = store.getVariablesValues();
        this.initialValueOfParameters = store.getParametersValues();
    }
    /**
     * Adds a transition to the model
     * @param t a new transition
     * @return the id of the transition
     */
    public int addTransition(Transition t) {
        if (this.initialized)
            throw new ModelException("Cannot add a transition to an initialized model");
        if (t == null)
            throw new ModelException("Null transition added to the model");
        if (t.getType() == TType.CONTINUOUS || t.getType() == TType.HYBRID)
            throw new ModelException("Transition " + t.getEvent() + " has type unsupported by stochastic model");
        int id = this.numberOfTransitions;
        if (id == this.transitions.length)
            transitions = (Transition[]) Arrays.ensureCapacity(transitions, 2*transitions.length);
        this.transitions[id] = t;
        if (t.getType() == TType.STOCHASTIC) {
            if (this.numberOfStochasticTransitions == this.stochasticTransitionID.length)
                this.stochasticTransitionID = Arrays.ensureCapacity(stochasticTransitionID, 2*stochasticTransitionID.length);
            this.stochasticTransitionID[this.numberOfStochasticTransitions++]=id;
        }
        else if(t.getType() == TType.INSTANTANEOUS) {
            if (this.numberOfInstantaneousTransitions == this.instantaneousTransitionID.length)
                this.instantaneousTransitionID = Arrays.ensureCapacity(instantaneousTransitionID, 2*instantaneousTransitionID.length);
            this.instantaneousTransitionID[this.numberOfInstantaneousTransitions++]=id;
        }
        else if(t.getType() == TType.TIMED) {
             if (this.numberOfTimedTransitions == this.timedTransitionID.length)
                this.timedTransitionID = Arrays.ensureCapacity(timedTransitionID, 2*timedTransitionID.length);
            this.timedTransitionID[this.numberOfTimedTransitions++]=id;
        }
        numberOfTransitions++;
        return id;
    }
    /**
     *
     * @return true if the model has been initialized by calling  generateDependencyGraphs();
     */
    public boolean isInitialized() {
        return this.initialized;
    }

    public int getNumberOfTransitions() {
        return this.numberOfTransitions;
    }

    /**
     *
     * @return the number of stoch transitions
     */
    public int getNumberOfStochasticTransitions() {
        return this.numberOfStochasticTransitions;
    }
    /**
     *
     * @return the list of IDs of stochastic transitions
     */
    public int[] getListOfStochasticTransitionID() {
        return this.stochasticTransitionID;
    }
    /**
     *
     * @return the number of instantaneous transitions
     */
    public int getNumberOfInstantaneousTransitions() {
        return this.numberOfInstantaneousTransitions;
    }
    /**
     *
     * @return the list of IDs of instantaneous transitions
     */
    public int[] getListOfInstantaneousTransitionsID() {
        return this.instantaneousTransitionID;
    }
    /**
     *
     * @return the number of timed transitions
     */
    public int getNumberOfTimedTransitions() {
        return this.numberOfTimedTransitions;
    }
    /**
     *
     * @return the list of IDs of timed transitions;
     */
    public int[] getListOfTimedTransitionsID() {
        return this.timedTransitionID;
    }
    /**
     * Evaluates the guard of the indicated transition
     * @param transitionID the ID of the transition
     * @return true if the transition is active
     */
    public boolean evaluateGuard(int transitionID) {
        return transitions[transitionID].evaluateGuard();
        
    }
    /**
     * Return the current activation status of the specified transition
     * @param transitionID
     * @return true if the transition is active
     */
    public boolean getCurrentGuardStatus(int transitionID) {
        return transitions[transitionID].isActive();
    }
    /**
     * computes the rate of the indicated transition
     * @param transitionID
     * @return
     */
    public double computeRate(int transitionID) {
        return transitions[transitionID].computeRate();
    }
    /**
     * computes the next firing time of a timed transition. raises an exception if
     * the transition is not timed
     * @param transitionID
     * @param time the current simulation time
     * @ the retuned value is guaranteed to be >= time.
     */
    public double computeNextFiringTime(int transitionID, double time) {
        return transitions[transitionID].getNextFiringTime(time);
    }
    /**
     * Executes a given transition, modyfing the store.
     * @param transitionID
     */
    public void execute(int transitionID) {
        this.transitions[transitionID].execute(store);
    }
    /**
     *
     * @param transitionID
     * @return true if the specified transition is stopping
     */
    public boolean isStopping(int transitionID) {
        return this.transitions[transitionID].isStopping();
    }
    /**
     *
     * @param transitionID
     * @return true if the specified transition is delayed and is stopping after
     * a delay
     */
    public boolean isStoppingAfterDelay(int transitionID) {
        return this.transitions[transitionID].isDelayed() && this.transitions[transitionID].isStoppingAfterDelay();
    }
    /**
     *
     * @param transitionID
     * @return the type of a given transition
     */
    public TType getTransitionType(int transitionID) {
        return this.transitions[transitionID].getType();
    }
    /**
     *
     * @param transitionID
     * @return the name of a transition (the event name_
     */
    public String getTransitionName(int transitionID) {
        return this.transitions[transitionID].getEvent();
    }


    public void finalizeInitialization() {
        store.finalizeInitialization();
        transitions = (Transition[]) java.util.Arrays.copyOf(transitions, numberOfTransitions);
        stochasticTransitionID = java.util.Arrays.copyOf(stochasticTransitionID, numberOfStochasticTransitions);
        instantaneousTransitionID = java.util.Arrays.copyOf(instantaneousTransitionID, numberOfInstantaneousTransitions);
        timedTransitionID = java.util.Arrays.copyOf(timedTransitionID, numberOfTimedTransitions);
        this.generateDependencyGraphs();
        this.initialized = true;
    }


    /**
     * Constructs the dependency graphs;
     */
    private void generateDependencyGraphs() {
        //initialization
        ArrayList<ArrayList<Integer>> varGuard,varRate,varTime,transUpdate,timeDG,guardDG,rateDG;
        ArrayList<Integer> vars,trans;
        varGuard = new ArrayList();
        varRate = new ArrayList();
        varTime = new ArrayList();
        timeDG = new ArrayList();
        guardDG = new ArrayList();
        rateDG = new ArrayList();
        this.firingTimeDependencyGraph = new Integer[this.numberOfTransitions][];
        this.guardDependencyGraph = new Integer[this.numberOfTransitions][];
        this.rateDependencyGraph = new Integer[this.numberOfTransitions][];
        for (int i=0;i<this.store.getNumberOfVariables();i++) {
            varGuard.add(new ArrayList());
            varRate.add(new ArrayList());
            varTime.add(new ArrayList());
        }
        transUpdate = new ArrayList();
        for (int i=0;i<this.numberOfTransitions;i++) {
            transUpdate.add(new ArrayList());
            timeDG.add(new ArrayList());
            guardDG.add(new ArrayList());
            rateDG.add(new ArrayList());
        }
        //filling basic info
        for (int id=0;id<this.numberOfTransitions;id++) {
            vars = this.transitions[id].getGuardVariables();
            for (Integer v : vars)
                varGuard.get(v).add(id);
            vars = this.transitions[id].getRateVariables();
            for (Integer v : vars)
                varRate.get(v).add(id);
            vars = this.transitions[id].getNextFiringTimeVariables();
            for (Integer v : vars)
                varTime.get(v).add(id);
            vars = this.transitions[id].getUpdatedVariables();
            for (Integer v : vars)
                transUpdate.get(id).add(v);
        }
        //construct dependency graphs. for each transition id, and for each updated variable
        //add to the dep graph all the transitions depending on that var.
        for (int id=0;id<this.numberOfTransitions;id++) {
            vars = transUpdate.get(id);
            for (Integer v : vars) {
                trans = varGuard.get(v);
                for (Integer t : trans)
                    guardDG.get(id).add(t);
                trans = varRate.get(v);
                for (Integer t : trans)
                    if (GlobalOptions.alwaysComputeRateAfterGuard)
                        if (!guardDG.get(id).contains(t))
                            rateDG.get(id).add(t);
                    else
                        rateDG.get(id).add(t);
                trans = varTime.get(v);
                for (Integer t : trans)
                    if (GlobalOptions.alwaysComputeRateAfterGuard)
                        if (!guardDG.get(id).contains(t))
                            timeDG.get(id).add(t);
            }
        }
        for (int id=0;id<this.numberOfTransitions;id++) {
            this.guardDependencyGraph[id] = guardDG.get(id).toArray(new Integer[0]);
            this.rateDependencyGraph[id] = rateDG.get(id).toArray(new Integer[0]);
            this.firingTimeDependencyGraph[id] = timeDG.get(id).toArray(new Integer[0]);
        }
    }
    /**
     *
     * @param transitionID
     * @return the list of transitions whose guard is modified by the execution of the
     * transition with specified ID
     */
    public Integer[] getListOfUpdatedGuards(int transitionID) {
        //if (!this.initialized)
        //    throw new ModelException("Model has not been initialized");
        return this.guardDependencyGraph[transitionID];
    }
    /**
     *
     * @param transitionID
     * @return the list of transitions whose rate is modified by the execution of the
     * transition with specified ID
     */
    public Integer[] getListOfUpdatedRates(int transitionID) {
        //if (!this.initialized)
        //    throw new ModelException("Model has not been initialized");
        return this.rateDependencyGraph[transitionID];
    }
    /**
     *
     * @param transitionID
     * @return the list of transitions whose firing time is modified by the execution of the
     * transition with specified ID
     */
    public Integer[] getListOfUpdatedFiringTimes(int transitionID) {
        //if (!this.initialized)
        //    throw new ModelException("Model has not been initialized");
        return this.firingTimeDependencyGraph[transitionID];
    }
   /**
    * Raises an exception if a variable is not in the store
    * @param list a list of variable names
    * @return the ids of a list of variable names. Used for printing purposes
    */
    public ArrayList<Integer> getIdOfVariables(ArrayList<String> list) {
        ArrayList<Integer> ids = new ArrayList();
        for (String s : list)
            ids.add(store.getVariableID(s));
        return ids;
    }
    /**
     *
     * @return an array with the value of all variables of the store
     */
    public double[] getVariablesValues() {
        return store.getVariablesValues();
    }


    /**
     *
     * @return a list with the name of all variables in the store
     */
    public ArrayList<String> getNameOfAllVariables() {
        return this.store.getNameOfAllVariables();
    }

    /**
     * 
     * @return a list with the name of all parameters in the store
     */
    public ArrayList<String> getNameOfAllParameters() {
        return this.store.getNameOfAllParameters();
    }
    /**
     * 
     * @return the name of the model
     */
    public String getName() {
        return this.name;
    }
    /**
     * Changes the value of a variable. For batch purposes
     * @param var the name of the variable to modify.
     * @param value the new value
     */
    public void setValueOfVariable(String var, double value) {
        this.store.setVariableValue(this.store.getVariableID(var), value);
    }
     /**
     * Changes the value of a parameter. For batch purposes
     * @param param the name of the parameter to modify.
     * @param value the new value
     */
    public void setValueOfParameter(String param, double value) {
        this.store.setParameterValue(store.getParameterID(param), value);
    }
    /**
     * Resets the model to its initial state
     */
    public void resetToInitialState() {
        this.store.setAllVariableValues(initialValueOfVariables);
        this.store.setAllParameterValues(initialValueOfParameters);
    }

    public Predicate[] getTransitionDelayedGuards() {
        Predicate[] p = new Predicate[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            p[i] = transitions[i].getDelayedGuardPredicate();
        return p;
    }

    public AtomicReset[][] getTransitionDelayedResets() {
        AtomicReset[][] r = new AtomicReset[this.numberOfTransitions][];
        for (int i=0;i<numberOfTransitions;i++)
            r[i] = transitions[i].getDelayedResetList();
        return r;
    }

    public Function[] getTransitionDelays() {
        Function[] f = new Function[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            f[i] = transitions[i].getDelayFunction();
        return f;
    }

    public String[] getTransitionEvents() {
       String[] e = new String[this.numberOfTransitions];
       for (int i=0;i<numberOfTransitions;i++)
            e[i] = transitions[i].getEvent();
       return e;
    }

    public Predicate[] getTransitionGuards() {
        Predicate[] p = new Predicate[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            p[i] = transitions[i].getGuardPredicate();
        return p;
    }

    public Function[] getTransitionRates() {
        Function[] f = new Function[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            f[i] = transitions[i].getRateFunction();
        return f;
    }

    public AtomicReset[][] getTransitionResets() {
        AtomicReset[][] r = new AtomicReset[this.numberOfTransitions][];
        for (int i=0;i<numberOfTransitions;i++)
            r[i] = transitions[i].getResetList();
        return r;
    }

    public Function[] getTransitionTimedActivations() {
        Function[] f = new Function[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            f[i] = transitions[i].getTimedActivationFunction();
        return f;
    }

    public TType[] getTransitionTypes() {
        TType[] t = new TType[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            t[i] = transitions[i].getType();
        return t;
    }

    public boolean[] getTransitionStoppingStatus() {
        boolean [] s = new boolean[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            s[i] = transitions[i].isStopping();
        return s;
    }

    public boolean[] getTransitionStoppingStatusAfterDelay() {
        boolean [] s = new boolean[this.numberOfTransitions];
        for (int i=0;i<numberOfTransitions;i++)
            s[i] = transitions[i].isStoppingAfterDelay();
        return s;
    }

    public Store getStore() {
        return this.store;
    }
    
}
